using System;
using System.Collections;
using System.Collections.Generic;

namespace Fadd
{
	/// <summary>
	/// A dictionary mapping not only values but also sub nodes of the same 
	/// <see cref="MapHierarchy{KeyType,ValueType}"/> type
	/// </summary>
	public class MapHierarchy<KeyType, ValueType> : IEnumerable<KeyValuePair<KeyType, ValueType>>
	{
		private readonly Dictionary<KeyType, ValueType> _values = new Dictionary<KeyType, ValueType>();

		private readonly Dictionary<KeyType, MapHierarchy<KeyType, ValueType>> _children = new Dictionary<KeyType, MapHierarchy<KeyType, ValueType>>();

		private MapHierarchy<KeyType, ValueType> _parent;

		private KeyType _name;

		/// <summary>
		/// Instances the <see cref="MapHierarchy{KeyType,ValueType}"/>
		/// </summary>
		public MapHierarchy()
		{
		}

		/// <summary>
		/// Instances the <see cref="MapHierarchy{KeyType,ValueType}"/> with a given name that can be used to identify the map later
		/// </summary>
		/// <param name="name">A unique name in a hierarchy to set for the map</param>
		public MapHierarchy(KeyType name)
		{
			Check.Require(name, "name");
			_name = name;
		}

        /// <summary>
        /// Adds a value
        /// </summary>
        /// <param name="key">The key to map the value to</param>
        /// <param name="value">The value to add</param>
        /// <exception cref="ArgumentException">Thrown if a value has already been added with the given key</exception>
		public void Add(KeyType key, ValueType value)
		{
			Values.Add(key, value);
		}

		/// <summary>
		/// Updates or adds a value defined by the specified key
		/// </summary>
		/// <param name="key">The key of the value to update or add</param>
		/// <param name="value">The value to set for the specified key</param>
		public void Set(KeyType key, ValueType value)
		{
			if (Contains(key))
				_values[key] = value;
			else
				_values.Add(key, value);
		}

		/// <summary>
		/// Removes a value specified by the given key from the <see cref="MapHierarchy{KeyType,ValueType}"/>
		/// </summary>
		/// <param name="key">The key to remove the value for</param>
		/// <returns>True if a value was removed</returns>
		public bool Remove(KeyType key)
		{
			return _values.Remove(key);
		}

		#region Retrieval methods (Get and [])

		/// <summary>
		/// Sets or gets a value held by the map
		/// </summary>
		/// <param name="key">Key mapping the value</param>
		/// <returns>Null if the value wasn't found</returns>
		public ValueType this[KeyType key]
		{
			get { return _values[key]; }
			set { Set(key, value); }
		}

		/// <summary>
		/// Retrieves a child using a key
		/// </summary>
		/// <param name="key">The key the child is mapped to</param>
		/// <returns>Null if not found</returns>
		public MapHierarchy<KeyType, ValueType> GetChild(KeyType key)
		{
			return _children[key];
		}

		#endregion

		#region Contains methods

		/// <summary>
		/// Returns true if a value mapped to the given key is contained by the map
		/// </summary>
		/// <param name="key">Key the value is supposed to be mapped to</param>
		/// <returns>True if a key is found</returns>
		public bool Contains(KeyType key)
		{
			return _values.ContainsKey(key);
		}

		/// <summary>
		/// Returns true if a value is mapped
		/// </summary>
		/// <param name="value">The value to examine whether or not it is contained by the map</param>
		/// <returns>True if the value is found</returns>
		public bool ContainsValue(ValueType value)
		{
			return _values.ContainsValue(value);
		}

		/// <summary>
		/// Returns true if a child exists that is mapped with the given key
		/// </summary>
		/// <param name="key">The key to examine child mappings for</param>
		/// <returns>True if a child is found</returns>
		public bool ContainsMap(KeyType key)
		{
			Check.Require(key, "key");
			return _children.ContainsKey(key);
		}

		/// <summary>
		/// Returns true if the map contains the specified child
		/// </summary>
		/// <param name="map">The map to examine whether or not it is contained</param>
		/// <returns>True if the map is a child of the instance</returns>
		public bool ContainsMap(MapHierarchy<KeyType, ValueType> map)
		{
			Check.Require(map, "map");
			return _children.ContainsValue(map);

		}

		#endregion

		#region RemoveChild methods

		/// <summary>
		/// Removes a child if contained. Since the name set for the map will be used to remove the map a name must have been specified.
		/// </summary>
		/// <param name="map">The map to remove</param>
		/// <returns>True if the child was removed</returns>
		/// <remarks>If removed the <see cref="Parent"/> will be set to null for the map</remarks>
		public bool RemoveChild(MapHierarchy<KeyType, ValueType> map)
		{
			Check.Require(map, "map");
			Check.Require(map.Name, "To use the RemoveChild(MapHierarchy<KeyType, ValueType>) method the supplied map must have a defined Name.");

			if(_children.Remove(map.Name))
			{
				map._parent = null;
				return true;
			}

			return false;
		}

		/// <summary>
		/// Removes a child if contained
		/// </summary>
		/// <param name="key">The key or name of the child to remove</param>
		/// <returns>True if the child was removed</returns>
		/// <remarks>If removed the <see cref="Parent"/> will be set to null for the map removed</remarks>
		public bool RemoveChild(KeyType key)
		{
			Check.Require(key, "key");

			MapHierarchy<KeyType, ValueType> map;
			if(_children.TryGetValue(key, out map))
				map._parent = null;

			return _children.Remove(key);
		}

		#endregion

		#region AddChild methods

		/// <summary>
		/// Adds a child to the <see cref="MapHierarchy{KeyType,ValueType}"/> using the name specified in <see cref="Name"/>
		/// </summary>
		/// <param name="map">The map to add as a child, must have a defined se <see cref="Name"/></param>
		/// <exception cref="InvalidOperationException">
		/// Thrown if the map is already a child of another <see cref="MapHierarchy{KeyType,ValueType}"/> instance, 
		/// it must be removed from there manually before being added to avoid hiding logical errors in code
		/// </exception>
		public void AddChild(MapHierarchy<KeyType, ValueType> map)
		{
			Check.Require(map, "map");
			Check.Require(map.Name, "A HierarchyMap can only be added as a child if it has been assigned a name.");

			if (map.Parent != null)
				throw new InvalidOperationException("Map must be manually removed from previous parent before being added to a new parent.");

			Children.Add(map.Name, map);
			map._parent = this;
		}

		/// <summary>
		/// Adds a child to the <see cref="MapHierarchy{KeyType,ValueType}"/> using the name supplied
		/// </summary>
		/// <param name="map">The map to add as a child</param>
		/// <param name="key">The name to set for the map, will override any previous name</param>
		/// <exception cref="InvalidOperationException">
		/// Thrown if the map is already a child of another <see cref="MapHierarchy{KeyType,ValueType}"/> instance, 
		/// it must be removed from there manually before being added to avoid hiding logical errors in code
		/// </exception>
		public void AddChild(MapHierarchy<KeyType, ValueType> map, KeyType key)
		{
			Check.Require(map, "map");
			Check.Require(map.Name, "A HierarchyMap can only be added as a child if it has been assigned a name.");

			if(map.Parent != null)
				throw new InvalidOperationException("Map must be manually removed from previous parent before being added to a new parent.");

			map._name = key;
			Children.Add(map.Name, map);
			map._parent = this;
		}

		#endregion

		#region Map properties

		/// <summary>
		/// Retrieves all values held by the map
		/// </summary>
		public Dictionary<KeyType, ValueType> Values
		{
			get { return _values; }
		}

		/// <summary>
		/// Retrieves all child <see cref="MapHierarchy{KeyType,ValueType}"/> held by the map
		/// </summary>
		public Dictionary<KeyType, MapHierarchy<KeyType, ValueType>> Children
		{
			get { return _children; }
		}

		/// <summary>
		/// Retrieves the name identifying the map, is null if no name is set
		/// </summary>
		public KeyType Name
		{
			get { return _name; }
		}

		/// <summary>
		/// Retrieves any parent to the map, is null if no parent is set
		/// </summary>
		public MapHierarchy<KeyType, ValueType> Parent
		{
			get { return _parent; }
		}

		#endregion

		#region IEnumerator interface implementation

		/// <summary>
		/// Returns an enumerator that can iterate through all the values contained by the map
		/// </summary>
		public IEnumerator GetEnumerator()
		{
			return _values.GetEnumerator();
		}

		/// <summary>
		/// Returns an enumerator that can iterate through all the values contained by the map
		/// </summary>
		IEnumerator<KeyValuePair<KeyType, ValueType>> IEnumerable<KeyValuePair<KeyType, ValueType>>.GetEnumerator()
		{
			return _values.GetEnumerator();
		}

		#endregion
	}
}
